#include"Sock.hpp"
#include<sys/epoll.h>
#include<iostream>
#include<string>

#define SIZE 128 
#define NUM 64 
static void Usage(std::string proc)
{
    std::cout << "Usage "<<proc<<" port " <<std::endl; 
}
//之后我们是这样启动程序的: ./epoll_server port
int main(int argc,char* argv[])
{
    if(argc!=2)
    {
        Usage(argv[0]);
        exit(1);
    }
    //建立套接字,完成绑定监听的操作
    uint16_t port = atoi(argv[1]);
    int listen_sock = Sock::Socket();
    Sock::Bind(listen_sock,port);
    Sock::Listen(listen_sock);

    //创建epoll模型
    int epfd = epoll_create(SIZE);

    //将监听套接字添加到epoll模型中,并关心其读事件
    struct epoll_event ev;
    ev.data.fd = listen_sock;
    ev.events = EPOLLIN;//我们关心的是读取事件是否就绪

    epoll_ctl(epfd,EPOLL_CTL_ADD,listen_sock,&ev);//向epoll模型当中注册关于listen_sock的事件

    //事件循环
    struct epoll_event revs[NUM];
    for(;;)
    {
        int timeout = -1;
        int n = epoll_wait(epfd,revs,NUM,timeout);
        switch(n)
        {
            case 0:
                std::cout << "time out ..." << std::endl;
                break;
            case -1:  
                std::cerr << "epoll error ..." << std::endl;
                break;
            default:   
                std::cout << "有事件就绪啦!" << std::endl;
                //因为已经把就绪事件按顺序整合在数组中,并且n就是已经就绪的事件个数
                for(int i = 0;i<n;i++)
                {
                    int sock = revs[i].data.fd;//获取已经就绪的文件描述符
                    std::cout << "文件描述符: " << sock << " 上面有事件就绪啦" << std::endl;
                    if(revs[i].events & EPOLLIN) //读取事件就绪
                    {
                        std::cout << "文件描述符: " << sock << " 读事件就绪" << std::endl;
                        //判断是监听套接字还是普通文件描述符上的读事件就绪
                        if(sock == listen_sock) //连接事件就绪
                        {
                            std::cout << "文件描述符: " << sock << " 链接数据就绪" << std::endl;
                            //处理链接事件
                            int fd = Sock::Accept(listen_sock);
                            if(fd>=0)
                            {
                                std::cout << "获取新链接成功啦： " << fd << std::endl;
                                //此时不能立即读取,因为有链接到来,并不代表该链接上有数据
                                //如果一旦读取,但是数据不就绪,此时就要被阻塞了进程就被挂起了
                                //做法:将当前新链接的文件描述符添加到epoll模型当中
                                struct epoll_event _ev;
                                _ev.events = EPOLLIN; //只关心读   如果想关心读和写: EPOLLIN | EPOLLOUT
                                _ev.data.fd = fd;
                                epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &_ev); //新的fd托管给了epoll！
                                std::cout << "已经将" << fd << " 托管给epoll啦" << std::endl;
                            }
                            else 
                            {
                                std::cout <<"获取新的链接失败"<<std::endl;
                                continue;
                            }
                        }
                        else //普通文件描述符上的读取事件就绪
                        {
                            std::cout << "文件描述符: " << sock << "正常数据就绪" << std::endl;
                            char buffer[1024] = {0};
                            //最后一个参数是0表示阻塞读取
                            //但是此时不会被阻塞,因为现在这个文件描述符上的读事件是就绪的
                            //少读取一个字符,因为我们把读到的内容当成字符串,最后位置放\0
                            ssize_t s = recv(sock,buffer,sizeof(buffer)-1,0);
                            if(s>0)
                            {
                                buffer[s] = '\0';
                                std::cout << "client [" << sock << "]# " << buffer << std::endl;
                            }
                            else if(s == 0) //对端关闭链接
                            {
                                std::cout << "client quit " << sock << std::endl;
                                close(sock);
                                //将当前文件描述符从epoll模型中删除
                                epoll_ctl(epfd, EPOLL_CTL_DEL, sock, nullptr);
                                std::cout << "sock: " << sock << "delete from epoll success" << std::endl;
                            }
                            else    //读取失败
                            {
                                std::cout << "recv error" << std::endl;
                                //将当前文件描述符从epoll模型中删除
                                close(sock); 
                                epoll_ctl(epfd, EPOLL_CTL_DEL, sock, nullptr);
                                std::cout << "sock: " << sock << "delete from epoll success" << std::endl;
                            }
                        }
                    }
                    else if(revs[i].events & EPOLLOUT) //写事件就绪
                    {}
                    else //...
                    {  }
                }
                break;
        }
    }
    
    //最后不要忘记关闭监听套接字和epoll模型
    close(listen_sock);
    close(epfd);
    return 0;
}
